"""
数据可视化模块
用于生成各种图表和可视化展示
"""
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from wordcloud import WordCloud
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import jieba
from collections import Counter
import logging
from typing import Dict, List, Tuple
import os
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
import json
from matplotlib import font_manager

# 设置中文字体（默认优先常见中文字体名，后续会尝试动态加载本地字体文件）
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC', 'WenQuanYi Zen Hei', 'SimHei', 'Microsoft YaHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False


class KeywordVisualizer:
    """关键词可视化器"""
    
    def __init__(self, config: Dict):
        """
        初始化可视化器
        
        Args:
            config: 配置字典
        """
        self.config = config
        self.viz_config = config.get('visualization', {})
        self.logger = logging.getLogger(__name__)
        
        # 设置图表样式
        plt.style.use(self.viz_config.get('plots', {}).get('style', 'seaborn'))
        self.figsize = tuple(self.viz_config.get('plots', {}).get('figsize', [12, 8]))
        self.dpi = self.viz_config.get('plots', {}).get('dpi', 300)
        
        # 设置颜色调色板
        self.colors = sns.color_palette("husl", 12)
        
        # 确保输出目录存在
        self.output_dir = "data/results/visualizations"
        os.makedirs(self.output_dir, exist_ok=True)

        # 动态查找并加载可用的中文字体文件（用于避免中文缺字）
        self.font_path = self._find_cjk_font_path()
        if self.font_path:
            try:
                font_manager.fontManager.addfont(self.font_path)
                # 尝试将常见中文字体放在优先列表，确保渲染中文
                plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC', 'WenQuanYi Zen Hei', 'SimHei', 'Microsoft YaHei', 'DejaVu Sans']
                self.logger.info(f"已加载中文字体: {self.font_path}")
            except Exception as e:
                self.logger.warning(f"加载中文字体失败: {self.font_path}, 错误: {e}")
        else:
            self.logger.warning("未检测到本地中文字体文件，可能导致中文显示为方块或缺字。建议安装 Noto Sans CJK 或 WenQuanYi 字体。")

    def _find_cjk_font_path(self) -> str:
        """
        查找本地可用的中文字体文件路径
        Returns:
            字体文件路径或空字符串
        """
        candidates = [
            # Noto CJK
            "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
            "/usr/share/fonts/opentype/noto/NotoSansCJKsc-Regular.otf",
            "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
            "/usr/share/fonts/truetype/noto/NotoSansCJKsc-Regular.ttf",
            # WenQuanYi
            "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
            "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
            # 文泉驿/文鼎等其它常见中文字体
            "/usr/share/fonts/truetype/arphic/ukai.ttc",
            "/usr/share/fonts/truetype/arphic/uming.ttc",
            # Droid Fallback
            "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf",
        ]
        for path in candidates:
            if os.path.isfile(path):
                return path
        # 也尝试从配置指定
        font_path_cfg = self.viz_config.get('wordcloud', {}).get('font_path')
        if font_path_cfg and os.path.isfile(font_path_cfg):
            return font_path_cfg
        return ""
    
    def create_wordcloud(self, keywords: List[str], title: str = "关键词词云", 
                        filename: str = None) -> None:
        """
        创建词云图
        
        Args:
            keywords: 关键词列表
            title: 图表标题
            filename: 保存文件名
        """
        self.logger.info(f"生成词云图: {title}")
        
        # 合并关键词文本
        text = ' '.join(keywords)
        
        # 中文分词
        words = jieba.cut(text, cut_all=False)
        text_segmented = ' '.join(words)
        
        # 词云配置
        wordcloud_config = self.viz_config.get('wordcloud', {})
        
        # 创建词云
        wordcloud = WordCloud(
            width=wordcloud_config.get('width', 800),
            height=wordcloud_config.get('height', 600),
            max_words=wordcloud_config.get('max_words', 100),
            background_color=wordcloud_config.get('background_color', 'white'),
            font_path=self.font_path if self.font_path else None,  # 指定中文字体路径，避免中文缺字
            collocations=False,
            relative_scaling=0.5,
            colormap='viridis'
        ).generate(text_segmented)
        
        # 绘制图表
        plt.figure(figsize=self.figsize, dpi=self.dpi)
        plt.imshow(wordcloud, interpolation='bilinear')
        plt.axis('off')
        plt.title(title, fontsize=16, pad=20)
        plt.tight_layout()
        
        # 保存图表
        if filename:
            filepath = os.path.join(self.output_dir, f"{filename}.png")
            plt.savefig(filepath, dpi=self.dpi, bbox_inches='tight')
            self.logger.info(f"词云图已保存: {filepath}")
        
        plt.show()
    
    def plot_keyword_length_distribution(self, df: pd.DataFrame, filename: str = None) -> None:
        """
        绘制关键词长度分布图
        
        Args:
            df: 关键词数据DataFrame
            filename: 保存文件名
        """
        self.logger.info("绘制关键词长度分布图")
        
        fig, axes = plt.subplots(2, 2, figsize=(15, 10), dpi=self.dpi)
        
        # 1. 长度分布直方图
        axes[0, 0].hist(df['length'], bins=30, alpha=0.7, color=self.colors[0], edgecolor='black')
        axes[0, 0].set_title('关键词长度分布')
        axes[0, 0].set_xlabel('长度')
        axes[0, 0].set_ylabel('频次')
        axes[0, 0].grid(True, alpha=0.3)
        
        # 2. 词数分布
        if 'word_count' in df.columns:
            axes[0, 1].hist(df['word_count'], bins=20, alpha=0.7, color=self.colors[1], edgecolor='black')
            axes[0, 1].set_title('关键词词数分布')
            axes[0, 1].set_xlabel('词数')
            axes[0, 1].set_ylabel('频次')
            axes[0, 1].grid(True, alpha=0.3)
        
        # 3. 中文字符数分布
        if 'chinese_char_count' in df.columns:
            axes[1, 0].hist(df['chinese_char_count'], bins=20, alpha=0.7, color=self.colors[2], edgecolor='black')
            axes[1, 0].set_title('中文字符数分布')
            axes[1, 0].set_xlabel('中文字符数')
            axes[1, 0].set_ylabel('频次')
            axes[1, 0].grid(True, alpha=0.3)
        
        # 4. 复杂度分布
        if 'complexity_score' in df.columns:
            axes[1, 1].hist(df['complexity_score'], bins=20, alpha=0.7, color=self.colors[3], edgecolor='black')
            axes[1, 1].set_title('关键词复杂度分布')
            axes[1, 1].set_xlabel('复杂度分数')
            axes[1, 1].set_ylabel('频次')
            axes[1, 1].grid(True, alpha=0.3)
        
        plt.tight_layout()
        
        if filename:
            filepath = os.path.join(self.output_dir, f"{filename}.png")
            plt.savefig(filepath, dpi=self.dpi, bbox_inches='tight')
            self.logger.info(f"长度分布图已保存: {filepath}")
        
        plt.show()
    
    def plot_category_distribution(self, df: pd.DataFrame, filename: str = None) -> None:
        """
        绘制分类分布图
        
        Args:
            df: 分类结果DataFrame
            filename: 保存文件名
        """
        self.logger.info("绘制分类分布图")
        
        fig, axes = plt.subplots(2, 2, figsize=(16, 12), dpi=self.dpi)
        
        # 1. 基于规则的分类分布
        if 'rule_based_category' in df.columns:
            rule_counts = df['rule_based_category'].value_counts()
            axes[0, 0].pie(rule_counts.values, labels=rule_counts.index, autopct='%1.1f%%',
                          colors=self.colors[:len(rule_counts)])
            axes[0, 0].set_title('基于规则的分类分布')
        
        # 2. 机器学习分类分布
        if 'ml_category' in df.columns:
            ml_counts = df['ml_category'].value_counts()
            axes[0, 1].pie(ml_counts.values, labels=ml_counts.index, autopct='%1.1f%%',
                          colors=self.colors[:len(ml_counts)])
            axes[0, 1].set_title('机器学习分类分布')
        
        # 3. 聚类分布
        if 'cluster_id' in df.columns:
            cluster_counts = df['cluster_id'].value_counts().head(10)  # 显示前10个聚类
            axes[1, 0].bar(range(len(cluster_counts)), cluster_counts.values, color=self.colors[:len(cluster_counts)])
            axes[1, 0].set_title('聚类分布 (前10个)')
            axes[1, 0].set_xlabel('聚类ID')
            axes[1, 0].set_ylabel('关键词数量')
            axes[1, 0].set_xticks(range(len(cluster_counts)))
            axes[1, 0].set_xticklabels(cluster_counts.index)
        
        # 4. 分类置信度分布
        if 'classification_confidence' in df.columns:
            axes[1, 1].hist(df['classification_confidence'], bins=20, alpha=0.7, 
                           color=self.colors[4], edgecolor='black')
            axes[1, 1].set_title('分类置信度分布')
            axes[1, 1].set_xlabel('置信度')
            axes[1, 1].set_ylabel('频次')
            axes[1, 1].grid(True, alpha=0.3)
        
        plt.tight_layout()
        
        if filename:
            filepath = os.path.join(self.output_dir, f"{filename}.png")
            plt.savefig(filepath, dpi=self.dpi, bbox_inches='tight')
            self.logger.info(f"分类分布图已保存: {filepath}")
        
        plt.show()
    
    def plot_cluster_analysis(self, df: pd.DataFrame, features: np.ndarray = None, 
                             filename: str = None) -> None:
        """
        绘制聚类分析图
        
        Args:
            df: 数据DataFrame
            features: 特征矩阵
            filename: 保存文件名
        """
        self.logger.info("绘制聚类分析图")
        
        if 'cluster_id' not in df.columns:
            self.logger.warning("数据中没有聚类信息")
            return
        
        fig = plt.figure(figsize=(16, 12), dpi=self.dpi)
        
        # 如果有特征矩阵，进行降维可视化
        if features is not None:
            # PCA降维
            pca = PCA(n_components=2)
            features_pca = pca.fit_transform(features)
            
            # t-SNE降维
            tsne = TSNE(n_components=2, random_state=42, perplexity=30)
            features_tsne = tsne.fit_transform(features[:1000])  # 限制数量以提高速度
            
            # 创建子图
            gs = fig.add_gridspec(2, 2, hspace=0.3, wspace=0.3)
            
            # PCA可视化
            ax1 = fig.add_subplot(gs[0, 0])
            scatter = ax1.scatter(features_pca[:, 0], features_pca[:, 1], 
                                c=df['cluster_id'], cmap='tab10', alpha=0.6)
            ax1.set_title('PCA聚类可视化')
            ax1.set_xlabel(f'PC1 ({pca.explained_variance_ratio_[0]:.2%} variance)')
            ax1.set_ylabel(f'PC2 ({pca.explained_variance_ratio_[1]:.2%} variance)')
            plt.colorbar(scatter, ax=ax1)
            
            # t-SNE可视化
            ax2 = fig.add_subplot(gs[0, 1])
            scatter2 = ax2.scatter(features_tsne[:, 0], features_tsne[:, 1], 
                                 c=df['cluster_id'][:1000], cmap='tab10', alpha=0.6)
            ax2.set_title('t-SNE聚类可视化')
            ax2.set_xlabel('t-SNE 1')
            ax2.set_ylabel('t-SNE 2')
            plt.colorbar(scatter2, ax=ax2)
        else:
            gs = fig.add_gridspec(2, 1, hspace=0.3)
        
        # 聚类大小分布
        ax3 = fig.add_subplot(gs[1, :] if features is not None else gs[0, :])
        cluster_sizes = df['cluster_id'].value_counts().sort_index()
        bars = ax3.bar(cluster_sizes.index, cluster_sizes.values, color=self.colors[:len(cluster_sizes)])
        ax3.set_title('各聚类大小分布')
        ax3.set_xlabel('聚类ID')
        ax3.set_ylabel('关键词数量')
        
        # 添加数值标签
        for bar in bars:
            height = bar.get_height()
            ax3.text(bar.get_x() + bar.get_width()/2., height,
                    f'{int(height)}', ha='center', va='bottom')
        
        if filename:
            filepath = os.path.join(self.output_dir, f"{filename}.png")
            plt.savefig(filepath, dpi=self.dpi, bbox_inches='tight')
            self.logger.info(f"聚类分析图已保存: {filepath}")
        
        plt.show()
    
    def create_interactive_dashboard(self, df: pd.DataFrame, filename: str = None) -> None:
        """
        创建交互式仪表板
        
        Args:
            df: 数据DataFrame
            filename: 保存文件名
        """
        self.logger.info("创建交互式仪表板")
        
        # 创建子图
        fig = make_subplots(
            rows=2, cols=2,
            subplot_titles=('关键词长度分布', '分类分布', '聚类大小', '置信度分布'),
            specs=[[{"type": "histogram"}, {"type": "pie"}],
                   [{"type": "bar"}, {"type": "histogram"}]]
        )
        
        # 1. 关键词长度分布
        fig.add_trace(
            go.Histogram(x=df['length'], name='长度分布', nbinsx=30),
            row=1, col=1
        )
        
        # 2. 分类分布饼图
        if 'ml_category' in df.columns:
            category_counts = df['ml_category'].value_counts()
            fig.add_trace(
                go.Pie(labels=category_counts.index, values=category_counts.values, name="分类"),
                row=1, col=2
            )
        
        # 3. 聚类大小条形图
        if 'cluster_id' in df.columns:
            cluster_counts = df['cluster_id'].value_counts().head(10)
            fig.add_trace(
                go.Bar(x=cluster_counts.index, y=cluster_counts.values, name='聚类大小'),
                row=2, col=1
            )
        
        # 4. 置信度分布
        if 'classification_confidence' in df.columns:
            fig.add_trace(
                go.Histogram(x=df['classification_confidence'], name='置信度', nbinsx=20),
                row=2, col=2
            )
        
        # 更新布局
        fig.update_layout(
            title_text="长尾关键词分析仪表板",
            showlegend=False,
            height=800
        )
        
        if filename:
            filepath = os.path.join(self.output_dir, f"{filename}.html")
            fig.write_html(filepath)
            self.logger.info(f"交互式仪表板已保存: {filepath}")
        
        fig.show()
    
    def plot_top_keywords_by_category(self, df: pd.DataFrame, top_n: int = 10, 
                                     filename: str = None) -> None:
        """
        绘制各分类下的热门关键词
        
        Args:
            df: 数据DataFrame
            top_n: 显示的关键词数量
            filename: 保存文件名
        """
        self.logger.info("绘制各分类热门关键词")
        
        if 'ml_category' not in df.columns:
            self.logger.warning("数据中没有分类信息")
            return
        
        categories = df['ml_category'].value_counts().head(6).index  # 取前6个分类
        
        fig, axes = plt.subplots(2, 3, figsize=(18, 12), dpi=self.dpi)
        axes = axes.flatten()
        
        for i, category in enumerate(categories):
            if i >= 6:
                break
            
            category_df = df[df['ml_category'] == category]
            
            # 如果有置信度信息，按置信度排序
            if 'classification_confidence' in category_df.columns:
                top_keywords = category_df.nlargest(top_n, 'classification_confidence')
            else:
                top_keywords = category_df.head(top_n)
            
            # 绘制水平条形图
            y_pos = np.arange(len(top_keywords))
            if 'classification_confidence' in top_keywords.columns:
                values = top_keywords['classification_confidence']
                xlabel = '置信度'
            else:
                values = np.ones(len(top_keywords))
                xlabel = '数量'
            
            axes[i].barh(y_pos, values, color=self.colors[i % len(self.colors)])
            axes[i].set_yticks(y_pos)
            axes[i].set_yticklabels(top_keywords['keyword'], fontsize=8)
            axes[i].set_xlabel(xlabel)
            axes[i].set_title(f'{category} (前{top_n}个)')
            axes[i].grid(True, alpha=0.3)
        
        # 隐藏多余的子图
        for i in range(len(categories), 6):
            axes[i].set_visible(False)
        
        plt.tight_layout()
        
        if filename:
            filepath = os.path.join(self.output_dir, f"{filename}.png")
            plt.savefig(filepath, dpi=self.dpi, bbox_inches='tight')
            self.logger.info(f"热门关键词图已保存: {filepath}")
        
        plt.show()
    
    def generate_visualization_report(self, df: pd.DataFrame) -> Dict:
        """
        生成可视化报告
        
        Args:
            df: 数据DataFrame
        
        Returns:
            可视化统计报告
        """
        report = {
            'total_visualizations': 0,
            'generated_files': [],
            'data_summary': {
                'total_keywords': len(df),
                'avg_length': df['length'].mean() if 'length' in df.columns else 0,
                'unique_categories': df['ml_category'].nunique() if 'ml_category' in df.columns else 0,
                'unique_clusters': df['cluster_id'].nunique() if 'cluster_id' in df.columns else 0
            }
        }
        
        return report
    
    def create_all_visualizations(self, df: pd.DataFrame, features: np.ndarray = None) -> None:
        """
        创建所有可视化图表
        
        Args:
            df: 数据DataFrame
            features: 特征矩阵
        """
        self.logger.info("开始生成所有可视化图表...")
        
        # 1. 词云图
        self.create_wordcloud(df['keyword'].tolist(), filename='keyword_wordcloud')
        
        # 2. 长度分布图
        self.plot_keyword_length_distribution(df, filename='length_distribution')
        
        # 3. 分类分布图
        if 'ml_category' in df.columns:
            self.plot_category_distribution(df, filename='category_distribution')
        
        # 4. 聚类分析图
        if 'cluster_id' in df.columns:
            self.plot_cluster_analysis(df, features, filename='cluster_analysis')
        
        # 5. 热门关键词图
        if 'ml_category' in df.columns:
            self.plot_top_keywords_by_category(df, filename='top_keywords_by_category')
        
        # 6. 交互式仪表板
        self.create_interactive_dashboard(df, filename='interactive_dashboard')
        
        self.logger.info("所有可视化图表生成完成")


def main():
    """测试函数"""
    from utils import load_config, setup_logging
    
    # 加载配置
    config = load_config()
    logger = setup_logging(config)
    
    # 创建可视化器
    visualizer = KeywordVisualizer(config)
    
    try:
        # 读取分类结果数据
        df = pd.read_csv('data/results/classified_keywords.csv')
        print(f"读取到 {len(df)} 个分类后的关键词")
        
        # 生成所有可视化
        visualizer.create_all_visualizations(df)
        
        # 生成报告
        report = visualizer.generate_visualization_report(df)
        print("可视化报告:")
        for key, value in report.items():
            print(f"  {key}: {value}")
        
        print("可视化完成，图表已保存到 data/results/visualizations/ 目录")
        
    except FileNotFoundError:
        print("未找到分类结果文件，请先运行关键词分类")


if __name__ == "__main__":
    main()
